package com.lu.oss

import androidx.annotation.WorkerThread
import com.alibaba.sdk.android.oss.ClientException
import com.alibaba.sdk.android.oss.OSSClient
import com.alibaba.sdk.android.oss.ServiceException
import com.alibaba.sdk.android.oss.callback.OSSCompletedCallback
import com.alibaba.sdk.android.oss.model.HeadObjectRequest
import com.alibaba.sdk.android.oss.model.OSSRequest
import com.alibaba.sdk.android.oss.model.PutObjectRequest
import com.alibaba.sdk.android.oss.model.PutObjectResult
import com.alibaba.sdk.android.oss.model.ResumableDownloadRequest
import com.alibaba.sdk.android.oss.model.ResumableDownloadResult
import com.lu.oss.api.IOssObjectListener
import com.lu.oss.api.IOssService
import com.lu.oss.log.OssLocalLog
import com.lu.oss.tools.DigestTools
import java.io.File

/**
 *author: luqihua
 *date:2021/10/21
 *description:
 **/
class OssServiceImpl(
        private val mBucketName: String,
        private val mOssClient: OSSClient
) : IOssService {
    companion object {
        private const val TAG = "OssServiceImpl"
        const val PROGRESS_INTERVAL = 1500L
        private fun formatErrorMessage(
                ce: ClientException?,
                se: ServiceException?
        ): String {
            val messageBuilder = StringBuffer()
            // 请求异常
            if (ce != null) {
                // 本地异常如网络异常等
                ce.printStackTrace()
                messageBuilder.append("onFailure client : $ce")
            }
            if (se != null) {
                // 服务异常
                se.printStackTrace()
                messageBuilder.append("onFailure service : $se")
            }
            return messageBuilder.toString()
        }
    }

    @Volatile
    private var isRelease = false
    private val mOssObjectMap = HashMap<String, OssTaskInfo<*>>()

    @WorkerThread
    override fun downloadFile(
            objectKey: String,
            downloadPath: String,
            listener: IOssObjectListener?
    ) {

        if (isRelease) {
            OssLocalLog.d(TAG, "GET : this OssService is Release ")
            return
        }

        if (mOssObjectMap.containsKey(objectKey)) {
            OssLocalLog.w(TAG, "GET : objectKey=$objectKey is running")
            return
        }

        mOssObjectMap[objectKey] = OssTaskInfo(downloadPath,
                                               OssAction.GET,
                                               objectKey,
                                               null,
                                               listener = listener).also {
            doDownload(it)
        }
    }

    @WorkerThread
    override fun uploadFile(
            objectKey: String,
            filePath: String,
            listener: IOssObjectListener?
    ) {
        if (isRelease) {
            OssLocalLog.d(TAG, "PUT : this OssService is Release ")
            return
        }

        if (mOssObjectMap.containsKey(objectKey)) {
            OssLocalLog.w(TAG, "PUT : objectKey=$objectKey filePath=$filePath is running")
            return
        }

        val file = File(filePath)
        if (!file.exists()) {
            OssLocalLog.w(TAG, "PUT : filePath=$filePath doesn't exist")
            return
        }

        mOssObjectMap[objectKey] = OssTaskInfo(filePath,
                                               OssAction.PUT,
                                               objectKey,
                                               null,
                                               listener = listener).also {
            doUpload(it)
        }
    }

    @WorkerThread
    override fun uploadData(objectKey: String, data: ByteArray,
                            listener: IOssObjectListener?) {
        if (isRelease) {
            OssLocalLog.d(TAG, "PUT : this OssService is Release ")
            return
        }

        if (mOssObjectMap.containsKey(objectKey)) {
            OssLocalLog.w(TAG, "PUT : objectKey=$objectKey is running")
            return
        }

        if (data.isEmpty()) {
            OssLocalLog.w(TAG, "PUT : objectKey=$objectKey data is empty")
            return
        }

        mOssObjectMap[objectKey] = OssTaskInfo(data,
                                               OssAction.PUT,
                                               objectKey,
                                               null,
                                               listener = listener).also {
            doUpload(it)
        }
    }

    private fun doDownload(info: OssTaskInfo<*>) {
        val objectKey = info.objectKey
        val downloadPath = info.source as String
        val targetFile = File(downloadPath)

        //如果文件已经存在，检验文件的完整性  此过程因文件大小耗时不等  需放在io线程
        if (targetFile.exists() && targetFile.isFile) {
            val startTime = System.currentTimeMillis()
            val headRequest = HeadObjectRequest(mBucketName, info.objectKey)
            val headResult = mOssClient.headObject(headRequest)
            val md5 = headResult.metadata.contentMD5
            val fileMd5Base64 = DigestTools.getFileMd5Base64(targetFile)
            val endTime = System.currentTimeMillis()
            OssLocalLog.d(
                TAG,
                          "asyncGetObject : md5 = $md5 ; fileMd5=$fileMd5Base64; take time = ${endTime - startTime}")
            if (fileMd5Base64.equals(md5, true)) {
                OssLocalLog.w(TAG, "GET : objectKey=$objectKey already download")
                onComplete(objectKey)
                return
            }
            targetFile.delete()
        }

        OssLocalLog.d(TAG, "asyncGetObject : objectKey = $objectKey; downloadPath=$downloadPath")

        val request = ResumableDownloadRequest(mBucketName, objectKey, downloadPath).also {
            it.partSize = 1024 * 1024;
            it.enableCheckPoint = true
            it.crC64 = OSSRequest.CRC64Config.YES
            it.checkPointFilePath = targetFile.parentFile?.absolutePath ?: "/"
            it.setProgressListener { _, currentSize, totalSize ->
                onProgress(objectKey, currentSize, totalSize)
            }
        }
        info.task = mOssClient.asyncResumableDownload(
                request,
                object : OSSCompletedCallback<ResumableDownloadRequest,
                        ResumableDownloadResult> {
                    override fun onSuccess(
                            request: ResumableDownloadRequest,
                            result: ResumableDownloadResult) {
                        onComplete(request.objectKey)
                    }

                    override fun onFailure(
                            request: ResumableDownloadRequest,
                            ce: ClientException?,
                            se: ServiceException?) {
                        onOSSFailure(request.objectKey, ce, se)
                    }
                })
    }

    private fun doUpload(info: OssTaskInfo<*>) {

        val objectKey = info.objectKey

        val request = when (info.source) {
            is String -> PutObjectRequest(mBucketName, objectKey, info.source)
            is ByteArray -> PutObjectRequest(mBucketName, objectKey, info.source)
            else -> return
        }
        // 构造上传请求
        request.also {
            it.crC64 = OSSRequest.CRC64Config.YES
            it.setProgressCallback { request, currentSize, totalSize ->
                onProgress(request.objectKey, currentSize, totalSize)
            }
        }

        info.task = mOssClient.asyncPutObject(
                request,
                object : OSSCompletedCallback<PutObjectRequest,
                        PutObjectResult> {
                    override fun onSuccess(
                            request: PutObjectRequest,
                            result: PutObjectResult) {
                        onComplete(request.objectKey)
                    }

                    override fun onFailure(
                            request: PutObjectRequest,
                            ce: ClientException?,
                            se: ServiceException?) {
                        onOSSFailure(request.objectKey, ce, se)
                    }
                })
    }

    private fun retryTask(info: OssTaskInfo<*>) {
        OssLocalLog.w(TAG, "retryTask : objectKey=${info.objectKey} time=${info.retryTime}")
        Thread.sleep(info.retryTime * 5000L)
        when (info.ossAction) {
            OssAction.GET -> {
                doDownload(info)
            }

            OssAction.PUT -> {
                doUpload(info)
            }
        }
    }

    private fun onComplete(objectKey: String) {
        OssLocalLog.d(TAG, "${Thread.currentThread().name} onComplete : $objectKey")
        if (isRelease) return
        //下载成功，移除任务信息
        mOssObjectMap.remove(objectKey)?.listener?.onSuccess()
    }

    private fun onProgress(objectKey: String, curLen: Long, totalLen: Long) {
        if (isRelease) return
        mOssObjectMap[objectKey]?.also {
            val curTime = System.currentTimeMillis()
            if (curLen == totalLen) {
                OssLocalLog.d(
                    TAG, "   key=$objectKey : $curLen/$totalLen progress=100"
                )
                it.listener?.onProgress(100)
            } else if (curTime - it.preProgressTime > PROGRESS_INTERVAL) {
                val progress = (curLen * 100 / totalLen).toInt()
                OssLocalLog.d(
                    TAG,
                              "${Thread.currentThread().name} key=$objectKey : $curLen/$totalLen progress=$progress"
                )
                it.listener?.onProgress(progress)
                it.preProgressTime = curTime
            }
        }
    }


    /**
     * 结束指定对象的下载
     * @param objectKey
     */
    override fun stopTask(objectKey: String) {
        OssLocalLog.d(TAG, "stopTask : objectKey=$objectKey")
        mOssObjectMap.remove(objectKey)?.task?.cancel()
    }

    /**
     * 结束所有的异步任务
     */
    override fun release() {
        OssLocalLog.d(TAG, "hashCode=${hashCode()} release $isRelease")
        if (!isRelease) {
            isRelease = true
            try {
                for ((_, obj) in mOssObjectMap) {
                    obj.task?.also {
                        if (!it.isCanceled && !it.isCompleted) {
                            it.cancel()
                        }
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
            mOssObjectMap.clear()
        }
    }

    private fun onOSSFailure(
            objectKey: String,
            ce: ClientException?,
            se: ServiceException?
    ) {
        val message = formatErrorMessage(ce, se)
        OssLocalLog.e(
            TAG,
                      "${Thread.currentThread().name} onOSSFailure : objectKey=$objectKey error=$message")
        if (isRelease) return
        mOssObjectMap[objectKey]?.also { info ->
            if (info.canRetry()) {
                retryTask(info)
            } else {
                mOssObjectMap.remove(objectKey)
                info.listener?.onFailure(RuntimeException(message))
            }
        }
    }

}